<!doctype html>
<html>
  <head>
    {{.FeedbackButton}}
    <title>Switch to RotaNG</title>
    <style>
      .site {
        max-width: 960px;
        margin: auto;
      }

      .codeblock {
        display: block;
        white-space: pre-wrap;
      }

      .collapsible {
        background-color: #f3f3f3;
        color: black;
        cursor: pointer;
        padding: 4px;
        width: 100%;
        border: none;
        text-align: left;
        outline: none;
        font-size: 14px;
      }

      .active, .collapsible:hover {
        background-color: #f1f1f1;
      }

      .collapsible:after {
        content: '\002B';
        color: black;
        font-weight: bold;
        float: right;
        margin-left: 5px;
      }

      .active:after {
        content: "\2212";
      }

      p, span, li {
        font-size: 14px;
      }

      code {
        font-size: 12px;
        color: green;
      }

      .content {
        padding: 0 4px;
        max-height: 0;
        overflow: hidden;
        transition: max-height 0.2s ease-out;
        font-size: 14px;
      }
    </style>
  </head>
  <body>
    <script src="/static/dist/bundle.js"></script>
    <nav-bar page="migrate" logoutURL="{{.LogoutURL}}"></nav-bar>
    <h2> Switch from rotations.py to RotaNG</h2>
      This page describes the steps needed to migrate over from
      the legacy Chrome Sheriff tooling to RotaNG.


    <h4>- Fix Calendar Permissions</h4>
      <p>
        RotaNG similar to the legacy rotation needs access to the
        rotations calendars to manipulate events.<br>For this to
        work the rotations calendar needs to be shared with the RotaNG
        service account <br><br><b>rota-ng@appspot.gserviceaccount.com</b>.
      </p>
      <p>
    <p>
      Steps:
      <ol>
        <li>Make sure you're the owner of the calendar<br>
          <span>
            <button class="collapsible">Check ownership</button>
            <div class="content">
              <ul>
                {{if not .safe}}
                <p>
                  <strong>
                    Note: <code>{{.Rota}}</code> shares calendar with other rotations, permissions might already be fixed.
                    <br>
                    Click the <code>Test</code> button below to check.
                    <br>
                    If Test passes, skip to step 3.
                  </strong>
                </p>
                {{end}}
                <li>Go to Google <a href="https://calendar.google.com" targets="_blank">Calendar</a></li>
                <li>In the <code>"Add Calendar"</code> field put: <code>{{.Config.Config.Calendar}}</code></li>
                <li>Go to <code>"Options > Settings and sharing"</code> for the newly added calendar.</li>
                <li>Under "<code>Share with specific people</code>" if you see "<code>ADD PEOPLE</code>" and can set
                  "<code>Make changes to events</code>" you're fine.<br>
                    <span>
                      <em>If you can't find an owner for the calendar, file a bug under <b>Infra>Sheriffing>Rotations</b></em>
                    </span>
                </li>
              </ul>
            </div>
          </span>
        </li>
        <li>Share the Calendar<br>
          <span>
            <button class="collapsible">Give RotaNG permissions</button>
            <div class="content">
            <ul>
              {{if not .safe}}
                <li>
                  <em>
                    {{.Rota}} shares calendar with other rotations, the permissions might already be fixed.
                    Click the Test button below to check.
                  </em>
                </li>
              {{end}}
              <li> In the Calendar "Settings > Share with specific people".</li>
              <li> <code>ADD PEOPLE</code>  -> <code>rota-ng@appspot.gserviceaccount.com</code> -> <code>Make changes to events</code>
            </ul>
            </div>
          </span>
        </li>
        {{if .safe}}
          <li>Remove calendar permissions for the legacy service<br>
            <span>
              <button class="collapsible">Remove RotaNG permissions</button>
              <div class="content">
              <ul>
                <li> In the Calendar "Settings > Share with specific people".</li>
                <li> Remove <code>chromiumcalendar@gmail.com</code>
              </ul>
              </div>
            </span>
          </li>
        {{else}}
          <li>Avoid duplicate emails<br>
            <span>
              <button class="collapsible">Disable RotaNG nag emails</button>
              <div class="content">
                Until <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=889221">crbug/889221</a> is resolved the legacy
                service will keep sending nag e-mails.<br>
                To avoid duplicate e-mails the RotaNG emails can be disabled until the legacy email cron job is removed.
                <p>
                  <em>With crbug/889221 resolved this will be set back to the original value.</em>
                </p>
              <ul>
                <li> Click <a href="/modifyrota?name={{.Rota}}" target="_blank">Modify {{.Rota}}</a></li>
                <li> Set <code>Email Days Before Notify</code> to <code>0</code></li>
                <li> Submit</li>
              </ul>
              </div>
            </span>
          </li>
        {{end}}
      </ol>
    </p>

    <h5>Check permission changes:</h5>

    <rota-testcal rota={{.Rota}} safe={{.safe}}></rota-testcal>
    <h4>- Check Configuration</h4>
    <p>Update your rotation configuration if needed.</p>
    <p>Note:
      <em>
        If your rotation has made recent changes to the JSON configuration files it might not be reflected in RotaNG.<br>
        You could either make the changes directly in the RotaNG configuration or
        <a href="https://chromium.googlesource.com/infra/infra/+/master/go/src/infra/appengine/rotang/DOCUMENTATION.md#update-from-legacy-json-file">re-import</a> the current JSON file to RotaNG.
      </em>
    </p>
    <button class="collapsible">Configuration</button>
    <div class="content">
      <rota-create config="{{.ConfigJSON}}" generators="{{.Generators}}" modifiers="{{.Modifiers}}">
      </rota-create>
    </div>
    <button class="collapsible">Documentation</button>
    <div class="content">
      <ul>
        <ul>
          <li>
            <p>
              <strong>Rotation Name</strong>
            </p>
            <p>
              <em>Changing the name of a rotation creates a copy of the current rotation with the specified name</em>
            </p>
            <p>This field is also used when creating calendar entries.</p>
          </li>
          <li>
            <p>
              <strong>Description</strong>
            </p>
            <p>Describes the rotation. Also used for the summary of calendar events.
              See the Email templates for more information about how to fill out this field.
            </p>
          </li>
          <li>
            <p>
              <strong>Calendar</strong>
            </p>
            <p>This should contain the id of the team shared calendar.
            </p>
            <p>Eg. <code>{{.Config.Config.Calendar}}</code>
            </p>
          </li>
          <li>
            <p>
            <strong>Owners</strong>
            </p>
            <p>A comma separated list of rotation owners.
            </p>
            <p>Note: <em>This will be changed to use mdb groups shortly</em>
            </p>
          </li>
          <li>
            <p><strong>Expiration</strong>
            </p>
            <p>This specifies when to automatically schedule new shifts. Setting this to 0 gives no automatic shifts will be scheduled.</p>
            <p>Note: <em>Nothing will be scheduled before the rotation is enabled</em>
            </p>
          </li>
          <li>
            <p><strong>Shifts To Schedule</strong></p>
            <p>Defines how many shifts to be scheduled when Expired.</p>
          </li>
          <li>
            <p><strong>Email Subject Template</strong></p>
            <p>The subject of the emails sent from the rotation service to upcoming oncallers.</p>
            <p>This uses <a href="https://godoc.org/text/template">Go templates</a> to generated the final message.</p>
            <p>The structures used to feed the template looks like this.</p>
            <code class="codeblock">
              type Info struct {
                RotaName    string
                ShiftConfig ShiftConfig
                ShiftEntry  ShiftEntry
                Member      Member
                MemberURL   string
              }

              type ShiftConfig struct {
                // StartTime represents the start-time of the first shift.
                // Only the Time of day is considered.
                // Defaults to 00:00 PDT.
                StartTime time.Time
                // Length sets the number of days a shift lasts.
                Length int
                // Skip defines a number of days with no oncalls.
                Skip int
                // Shifts represents the shifts over a 24hour period.
                Shifts []Shift
                // ShiftMembers specifides number of members per shift.
                ShiftMembers int
                // Generator used to schedule new shifts.
                Generator string
              }

              type ShiftEntry struct {
                // Name of the Shift this entry belongs to.
                Name string
                // OnCall are the members on-call for this shift.
                OnCall    []ShiftMember
                StartTime time.Time
                EndTime   time.Time
                // Comment is an optional comment where the rota algo
                // can add some extra information.
                Comment string
                // EvtID can be used to match an event to a calendar event.
                EvtID string
              }

              type Member struct {
                Name        string
                Email       string
                TZ          time.Location
                OOO         []OOO
                Preferences []Preference
              }
          </code>
          <p>This gives that you can have any information from the <code>Info</code> struct inserted in your message.</p>
          <p>Eg.</p>
          <code>Upcoming On-call shift for rotation: {{"{{.RotaName}}"}} {{"{{.ShiftEntry.StartTime.In .Member.TZ}}"}} to {{"{{.ShiftEntry.EndTime.In .Member.TZ}}"}}
            </code>
            <p>Will produce:</p>
            <code>Upcoming On-call shift for rotation: Name of rota 2006-04-05 17:00:00 -0700 PDT to 2006-04-06 17:00:00 -0700 PDT</code>
            <p>For a member with a US Pacific TZ.</p>
            <p><strong>Note</strong>: The current <code>%s</code> format replacement will not work. You could replace the <code>%s</code> with <code>{{"{{.RotaName}}"}}</code> for something similar.</p>
          </li>
          <li>
            <p><strong>Email Body Template</strong></p>
            <p>See Email Subject Template.</p>
          </li>
          <li><p><strong>Email Days Before Notify</strong></p>
            <p>Number of days before an on-call shift to send mails out.</p>
            <p>Note: <em>No mails will be produced until the rotation is enabled.</em></p>
          </li>
          <li>
            <p><strong>Members</strong></p>
            <p>Manage members of the rotation.</p>
            <ul>
              <li>
                <p><strong>Name</strong></p>
                <p>The name of the member.</p>
              </li>
              <li>
                <p><strong>Email</strong></p>
                <p>email address of the member</p>
              </li>
              <li>
                <p><strong>Shift</strong></p>
                <p>Identifies the member shift</p>
              </li>
              <li>
                <p><strong>TZ</strong></p>
                <p>Member TimeZone</p>
                <p>Note:
                  <em>
                    When importing members from the legacy JSON configuration files the following applies for members:<br>
                    - members in <code>rotation_list_pacific</code> - TZ set to <code>America/Los_Angeles</code>.<br>
                    - members in <code>rotation_list_default</code> - TZ set to <code>UTC</code>.<br>
                  </em>
                </p>
              </li>
            </ul>
          </li>
          <li><p><strong>Shift StartTime</strong></p>
            <p>Specifies the start in MTV time of the first shift.</p>
          </li>
          <li>
            <p><strong>Length</strong></p>
            <p>Number of days a shift spans.</p>
          </li>
          <li>
            <p><strong>Skip</strong></p>
            <p>Skip in the shift schedule. Eg. if you rotation is weekdays only you'd set this to Length/Skip: 5/2</p>
          </li>
          <li>
            <p><strong>ShiftMembers</strong></p>
            <p>Number of members to schedule per shift.</p>
          </li>
          <li>
            <p><strong>Generator</strong></p>
            <p>Specifies what Generator to use when scheduling shifts</p>
            <ul>
              <li><p><strong>Fair</strong></p>
                <p>Takes previous shifts into account when scheduling shifts. If there are no previous shifts it'll fall back to random.</p>
              </li>
              <li><p><strong>Random</strong></p></li>
              <li><p><strong>Legacy</strong></p><p>Tries to emulate the current scheduler.</p></li>
            </ul>
          </li>
          <li><p><strong>Shifts</strong></p>
            <p>The service supports split shifts so you can have follow the sun kind of configurations with MTV -&gt; SYD -&gt; EU.</p>
            <p>This would be configured as 3 shifts.</p>
            <code class="codeblock">
                  Name: MTV OnCall  Duration: 8
                  Name: SYD OnCall  Duration: 8
                  Name: EU OnCall   Duration: 8
            </code>
            <p>When scheduling the RotaMembers <code>ShiftsName</code> would be used to figure out which users to put into which shift.</p>
            <p>It's also OK to have empty splits Eg.</p>
            <code class="codeblock">
                  Name: MTV Office      Duration: 6
                  Name: MTV-&gt;SYD no-one Duration: 2
                  Name: SYD Office      Duration: 6
                  Name: SYD-&gt;EU no-one  Duration: 2
                  Name: EU Office       Duration: 6
                  Name: EU-&gt;MTV no-one  Duration: 2
            </code>
            <p>The calendar entries created will have the Shift name added to the description.</p>
            <p>Note: <em>The sum of the shift durations should always be 24</em></p>
          </li>
        </ul>
      </ul>
    </div>
    <h4>- Import Shifts</h4>
    <p>
      With the permissions in place, the next step is to import the legacy schedules.<br>
      Import the shifts by clicking the <b>Import Shifts</b> button below.<br>
      if everything looks in order, <b>Submit</b> the shifts.
    <br><br>
    </p>
      <rota-shiftimport rota="{{.Rota}}"></rota-shiftimport>
      <br>
      <br>
      <p>
        To mange shifts in the new service, follow the link below.
      </p>
      <a href="/manageshifts?name={{.Rota}}" target="_blank">
        Manage imported shifts
      </a>
      <br>
      <em><b>Note:</b> Shifts created before enabling the rotation will not show up immediately; after enabling the rotation it can take up to an hour before the calendar is updated.</em>
    <h4>- Test Email</h4>
    <p>
      Test out the Email templates.
    </p>
    <p>
    <em>Note: Sent emails will be sent to the current logged in user.</em>
    <em>Note: Update configuration in the previous step and test again if it does not look as expected.</em>
    </p>
    <rota-testemail rota={{.Rota}}></rota-testemail>
    <h4>- Enable Rotation</h4>
    <p>
      Enabling a rotation makes the new service start scheduling shifts and sending emails for upcoming shifts.
    </p>
    <button class="collapsible">To Rollback</button>
    <div class="content">
      <ul>
        {{if .safe}}
          <li>Add back <code>chromiumcalendar@gmail.com</code> to <code>{{.Config.Config.Calendar}}</code></li>
          <li>Give permission <code>Make changes to events</code></li>
          <li>Disable the rotation at <a href="/managerota" target="_blank">https://rota-ng.appspot.com/managerota</a></li>
          <li>File a <a href="https://bugs.chromium.org/p/chromium/issues/list?can=2&q=component%3AInfra%3ESheriffing%3ERotations+&colspec=ID+Pri+M+Stars+ReleaseBlock+Component+Status+Owner+Summary+OS+Modified&x=m&y=releaseblock&cells=ids">bug</a> in Component <b>Infra>Sheriffing>Rotations</b>
        {{else}}
          <li>Disable the rotation at <a href="/managerota" target="_blank">https://rota-ng.appspot.com/managerota</a></li>
          <li>File a <a href="https://bugs.chromium.org/p/chromium/issues/list?can=2&q=component%3AInfra%3ESheriffing%3ERotations+&colspec=ID+Pri+M+Stars+ReleaseBlock+Component+Status+Owner+Summary+OS+Modified&x=m&y=releaseblock&cells=ids">bug</a> in Component <b>Infra>Sheriffing>Rotations</b>
        {{end}}
      </ul>
    </div>
    <br>
    <form action="enabledisable" method="post">
      <button type="submit" name="name" value="{{.Rota}}">Enable Rotation</button>
    </form>

    <script>
      var coll = document.getElementsByClassName("collapsible");
      var i;

      for (i = 0; i < coll.length; i++) {
        coll[i].addEventListener("click", function() {
          this.classList.toggle("active");
          var content = this.nextElementSibling;
          if (content.style.maxHeight){
            content.style.maxHeight = null;
          } else {
            content.style.maxHeight = content.scrollHeight + "px";
          }
        });
      }
      </script>
  </body>
</html>
